﻿using System;
using System.Data.Common;
using MvcMiniProfiler;
using System.Linq;
using System.Reflection;

namespace MvcMiniProfiler.Data
{
    public class ProfiledDbProviderFactory {
        private static System.Collections.Concurrent.ConcurrentDictionary<string, DbProviderFactory> factories = new System.Collections.Concurrent.ConcurrentDictionary<string, DbProviderFactory>(StringComparer.InvariantCultureIgnoreCase);
        internal static readonly string prefix = typeof(ProfiledDbConnection).Namespace + '/';

        public static DbProviderFactory Lookup(string providerName) {
            return factories.GetOrAdd(providerName, GetProvider);
        }

        private static bool _Registered;
        public static void RegisterProviders(){
            if (_Registered) return;
            _Registered = true;
            foreach(System.Configuration.ConnectionStringSettings settings in System.Configuration.ConfigurationManager.ConnectionStrings){
                Lookup(settings.ProviderName); 
            } 
        }

        private static DbProviderFactory GetProvider(string providerName) {
            if (providerName.StartsWith(prefix )) {
                var names = providerName.Split('/');
                var tail = System.Data.Common.DbProviderFactories.GetFactory(names[1]);
                return GetProvider(providerName, tail);
            } else {
                 return System.Data.Common.DbProviderFactories.GetFactory(providerName);
            }        
        }

        public static DbProviderFactory GetProvider(DbProviderFactory tail) {
            var type = tail.GetType();
            var providerName = prefix + type.Namespace;
            return GetProvider(providerName, tail);
        }

        private static DbProviderFactory GetProvider(string providerName, DbProviderFactory tail) {
            DbProviderFactory instance;
            if (factories.TryGetValue(providerName, out instance))
                return instance;

            var type = typeof(ProfiledDbProviderFactory<>);
            type = type.MakeGenericType(tail.GetType());
            instance = Activator.CreateInstance(type, tail) as DbProviderFactory; //slow, but only happens once

            //register the provider
            var field = typeof(System.Data.Common.DbProviderFactories).GetField("_providerTable", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
            var table = System.Data.Common.DbProviderFactories.GetFactoryClasses(); //Initializes the value of _providerTable, but returns a copy of it
            table = field.GetValue(null) as System.Data.DataTable;

            if (!table.Rows.Cast<System.Data.DataRow>().Any(x => x[1] as string == type.Namespace))
                table.Rows.Add(type.FullName, providerName, providerName, type.AssemblyQualifiedName);

            return instance;
        }
    }

    internal class ProfiledDbProviderFactory<TTail> : DbProviderFactory, IServiceProvider where TTail : DbProviderFactory
    {
        public static ProfiledDbProviderFactory<TTail> Instance;
        internal readonly TTail tail;
        public ProfiledDbProviderFactory(TTail tail)
        {
            this.tail = tail;
            Instance = this;
        }
        
        public override bool CanCreateDataSourceEnumerator
        {
            get
            {
                return tail.CanCreateDataSourceEnumerator;
            }
        }
        public override DbDataSourceEnumerator CreateDataSourceEnumerator()
        {
            return tail.CreateDataSourceEnumerator();
        }
        public override DbCommand CreateCommand()
        {
            return new ProfiledDbCommand(tail.CreateCommand(), null, MiniProfiler.Current);
        }
        public override DbConnection CreateConnection()
        {
            return new ProfiledDbConnection(tail.CreateConnection(), MiniProfiler.Current, this);
        }
        public override DbParameter CreateParameter()
        {
            return tail.CreateParameter();
        }
        public override DbConnectionStringBuilder CreateConnectionStringBuilder()
        {
            return tail.CreateConnectionStringBuilder();
        }
        public override DbCommandBuilder CreateCommandBuilder()
        {
            return tail.CreateCommandBuilder();
        }
        public override DbDataAdapter CreateDataAdapter()
        {
            return tail.CreateDataAdapter();
        }
        public override System.Security.CodeAccessPermission CreatePermission(System.Security.Permissions.PermissionState state)
        {
            return tail.CreatePermission(state);
        }

        /// <summary>
        /// Extension mechanism for additional services;  
        /// </summary>
        /// <returns>requested service provider or null.</returns>
        object IServiceProvider.GetService(Type serviceType)
        {
            IServiceProvider tailProvider = tail as IServiceProvider;
            if (tailProvider == null) return null;
            var svc = tailProvider.GetService(serviceType);
            if (svc == null) return null;

#if ENTITY_FRAMEWORK
            if (serviceType == typeof(DbProviderServices))
            {
                svc = new ProfiledDbProviderServices((DbProviderServices)svc, MiniProfiler.Current);
            }
#endif
            return svc;
        }
    }
}
